﻿#/usr/bin/python

import sys, Queue, PyQt4
from PyQt4 import QtCore
from PyQt4 import QtGui

import pywinusb.hid as hid
import random
from   time import sleep

# hid_device.vendor_id, hid_device.product_id

class Thread(QtCore.QThread):
    msg_ready = QtCore.pyqtSignal(list)
    def __init__(self, func):
        super(QtCore.QThread,self).__init__()
        self.func = func
    def run(self):
        while True:
            msg = []
            items = self.func()
            
            if items:
                for i in items:
                    msg.append(i)
                self.msg_ready.emit(msg)
            
            

class MainWidget(QtGui.QWidget):
    def __init__(self):
        super(QtGui.QWidget, self).__init__()
        
        self.currentDevice = 0   # 当前设备编号
        self.previewDevice = 0   # 之前设备编号
        self.HIDDevice = None    # 设备
        self.config_num = 0
        self.out_reports_id_list = []
        
        self.queue = Queue.Queue()  #创建队列
        
        
        
        self.txButton   = QtGui.QPushButton(u"发送")
        self.txButton.setEnabled(False)
        self.txLine     = QtGui.QLineEdit()
        self.txLine.textChanged.connect(self.send_data_check)
        self.clearText  = QtGui.QCheckBox(u"发送后清除")
        sendLayout = QtGui.QHBoxLayout()
        sendLayout.addWidget(self.txLine)
        sendLayout.addWidget(self.clearText)
        sendLayout.addWidget(self.txButton)
        
        self.hexCheck = QtGui.QRadioButton(u"十六进制")
        self.hexCheck.setChecked(True)
        self.decCheck = QtGui.QRadioButton(u"十进制")
        self.binCheck = QtGui.QRadioButton(u"二进制")
        
        self.hexCheck.clicked.connect(self.config_decode)
        self.decCheck.clicked.connect(self.config_decode)
        self.binCheck.clicked.connect(self.config_decode)
        numModeLayout = QtGui.QHBoxLayout()
        numModeLayout.addWidget(self.hexCheck)
        numModeLayout.addWidget(self.decCheck)
        numModeLayout.addWidget(self.binCheck)
        boxGroup = QtGui.QGroupBox()
        boxGroup.setLayout(numModeLayout)
        
        labelDataLength = QtGui.QLabel(u"数据长度:")
        self.dataLength = QtGui.QSpinBox()
        self.dataLength.setRange(1, 64)
        self.dataLength.setValue(16)
        
        idListLabel = QtGui.QLabel(u"ID列表")
        self.idList = QtGui.QComboBox()
        idListLayout = QtGui.QHBoxLayout()
        idListLayout.addWidget(idListLabel)
        
        idListLayout.addWidget(self.idList)
        dataLengthLayout = QtGui.QHBoxLayout()
        dataLengthLayout.addWidget(labelDataLength)
        dataLengthLayout.addWidget(self.dataLength)
        
        configLayout = QtGui.QHBoxLayout()
        configLayout.addWidget(boxGroup)
        configLayout.addStretch()
        configLayout.addLayout(idListLayout)
        configLayout.addLayout(dataLengthLayout)
        
        
        self.rxLine = QtGui.QTextBrowser()
        self.bar        = QtGui.QScrollBar()
        self.bar = self.rxLine.verticalScrollBar()
        self.bar.setValue(self.bar.maximum());
        
        self.rxDisplayMode = QtGui.QComboBox()
        self.rxDisplayMode.addItem(u"十六进制")
        self.rxDisplayMode.addItem(u"十进制")
        self.rxDisplayMode.addItem(u"二进制")
        
        rxModeLayout = QtGui.QHBoxLayout()
        rxModeLayout.addWidget(QtGui.QLabel(u"接收显示:"))
        rxModeLayout.addWidget(self.rxDisplayMode)
        
        recvLayout = QtGui.QHBoxLayout()
        
        
        recvLayout.addWidget(self.rxLine)
        
        self.label_device_detail = QtGui.QLabel(u"设备信息： \n")
        rxClear = QtGui.QPushButton(u"清除接收窗口")
        rxClear.clicked.connect(self.clearRxBrowser)
        rxbutton_layout = QtGui.QVBoxLayout()
        rxbutton_layout.addLayout(rxModeLayout)
        rxbutton_layout.addWidget(self.label_device_detail)
        rxbutton_layout.addStretch()
        rxbutton_layout.addWidget(rxClear)
        
        rxbutton_layout.addStretch()
        recvLayout.addLayout(rxbutton_layout)
        
        
        layout_editor = QtGui.QVBoxLayout()
        layout_editor.addLayout(sendLayout)
        layout_editor.addLayout(recvLayout)
        
        self.infoLabel = QtGui.QLabel("None")
        
        self.list = QtGui.QComboBox()  #下拉列表
        self.openButton = QtGui.QPushButton(u"打开")
        self.openButton.connect(self.openButton,QtCore.SIGNAL("clicked()"),self.device_open)
        
        layout_list   = QtGui.QHBoxLayout()
        layout_list.addWidget(self.list)
        layout_list.addWidget(self.infoLabel)
        layout_list.addWidget(self.openButton)
        layout_list.addStretch()
        
        self.inputTips = QtGui.QLabel(u"提示:")
        
        layout = QtGui.QVBoxLayout()
        layout.addLayout(layout_list)
        layout.addLayout(configLayout)
        layout.addLayout(layout_editor)
        layout.addWidget(self.inputTips)
        
        self.setLayout(layout)
        self.txButton.connect(self.txButton,QtCore.SIGNAL('clicked()'),self.report_send)
        filter = hid.HidDeviceFilter()
        self.all_devices = filter.get_devices()
        string = u" 设备数量: {0:d}".format(len(self.all_devices))
        self.infoLabel.setText(string) 
        
        for i in self.all_devices:
            id_information = "vId= 0x{0:04X}, pId= 0x{1:04X}, ppId= 0x{2:04X}".format(i.vendor_id, i.product_id, i.parent_instance_id)
            self.list.addItem(id_information)
        
        if self.all_devices:
            self.HIDDevice = self.all_devices[self.currentDevice]
            
        self.list.currentIndexChanged.connect(self.itemChanged)
        self.list.currentIndexChanged.connect(self.device_change)
        
        self.thread  = Thread(self.queue_monitor)
        self.thread.msg_ready.connect(self.rxTextBrowserUpdate)
        self.thread.start()
        self.setWindowTitle(u"PyQt USBHID Assist")
        self.setWindowIcon(QtGui.QIcon("penguin.ico"))
        
    def queue_monitor(self):
        if self.queue.qsize():
            try:
                msgs = self.queue.get()
                return msgs
            except Queue.Empty:
                pass
                
    def rxTextBrowserUpdate(self, item):
        func1 = lambda x: hex(x)[2:].zfill(2)
        func2 = lambda x: str(x)
        func3 = lambda x: bin(x)[2:].zfill(8)
        funcArray = [func1, func2, func3]
        list = map(funcArray[self.rxDisplayMode.currentIndex()], item)
        
        string = " "
        string = string.join(list)
        self.rxLine.append(string)
        self.bar.setValue(self.bar.maximum())
        
    def report_recv_handler(self, data):
        self.queue.put(data)
        
    def clearRxBrowser(self):
        self.rxLine.clear()
        
    def config_decode(self):
        checkObjects = [self.hexCheck, self.decCheck, self.binCheck]
        for i, box in enumerate(checkObjects):
            if box.isChecked():
                self.config_num = i

    def closeEvent(self, event):
        print "Close event"
        self.HIDDevice.close()
        
    def device_change(self):
        self.currentDevice = self.list.currentIndex() #获取当前设备索引号
        string = u"设备信息\n" + self.HIDDevice.product_name \
            + "\n" + self.HIDDevice.vendor_name + "\n" + self.HIDDevice.serial_number
        
        self.label_device_detail.setText(string)
        if self.previewDevice != self.currentDevice:
            self.openButton.setText(u"打开")
            self.txButton.setEnabled(False)
        else: 
            if self.HIDDevice.is_opened():
                self.openButton.setText(u"关闭")
                self.txButton.setEnabled(True)
            else:
                self.openButton.setText(u"打开")
                self.txButton.setEnabled(False)
            
        
    def device_open(self):
        # 与之前选择的设备相同 
        if self.previewDevice == self.currentDevice:
            if self.HIDDevice.is_opened():
                self.HIDDevice.close()
                self.out_reports_id_list = []
                self.idList.clear()
                self.txButton.setEnabled(False)
                self.openButton.setText(u"打开")
                print self.HIDDevice, "Closed"
            else:
                self.HIDDevice.open()
                self.HIDDevice.set_raw_data_handler(self.report_recv_handler)
                self.reports = self.HIDDevice.find_output_reports()
                in_reports   = self.HIDDevice.find_input_reports()
                
                for i in in_reports:
                    self.idList.addItem("IN:" + str(i.report_id))
                self.dataLength.setValue(self.HIDDevice.hid_caps.output_report_byte_length)
                
                for i in self.reports:
                    self.idList.addItem("OUT:" + str(i.report_id))
                    self.out_reports_id_list.append(i.report_id)
                    
                
                self.txButton.setEnabled(True)
                string = u"设备信息\n" + self.HIDDevice.product_name + "\n"+ self.HIDDevice.vendor_name + "\n" + self.HIDDevice.serial_number
                self.label_device_detail.setText(string)
                print self.HIDDevice, "opend"
                
                self.txButton.setEnabled(True)
                self.openButton.setText(u"关闭")
                
        else:
            self.HIDDevice.close()
            self.out_reports_id_list = []
            self.idList.clear()
            print self.HIDDevice, "Closed"
            self.HIDDevice = self.all_devices[self.currentDevice]
            self.previewDevice = self.currentDevice
            self.HIDDevice.open()
            print self.HIDDevice, "Opened"
            self.HIDDevice.set_raw_data_handler(self.report_recv_handler)
            self.reports = self.HIDDevice.find_output_reports()
            in_reports   = self.HIDDevice.find_input_reports()
            
            for i in in_reports:
                self.idList.addItem("IN:" + str(i.report_id))
                
            self.dataLength.setValue(self.HIDDevice.hid_caps.output_report_byte_length)
            for i in self.reports:
                self.idList.addItem("OUT:" + str(i.report_id))
                self.out_reports_id_list.append(i.report_id)
            self.txButton.setEnabled(True)
            self.openButton.setText(u"关闭")
    
    def report_send(self):
        string = self.txLine.text()
        if self.clearText.isChecked():
            self.txLine.clear()
        buff     = [0xff] * self.dataLength.value()
        
        num_list =  str(string).strip().split()
        num_base = [16, 10, 2]
        for i, num in enumerate(num_list) :
            buff[i] = int(num.upper(), num_base[self.config_num])
        if buff[0] in self.out_reports_id_list:
            self.HIDDevice.send_output_report(buff)
        else:
            self.txLine.setStyleSheet("QLineEdit {background-color: red}")
            self.inputTips.setText(u"<font color=red>提示: 待发送报告的ID号不存在 \n请确保第一个字节的数据是输出ID</font>")
            
    def send_data_check(self):
        if self.HIDDevice.is_opened():
            string = self.txLine.text()
            num_list =  str(string).strip().split()
            num_base = [16, 10, 2]
            buff     = [0xff] * self.dataLength.value()
            # 字符串解析为 int 列表
            for i, num in enumerate(num_list) :
                buff[i] = int(num.upper(), num_base[self.config_num])
                
            if (buff[0] in self.out_reports_id_list) or (len(string) == 0):
                self.txLine.setStyleSheet("QLineEdit {background-color: white}")
                self.inputTips.setText(u"提示:")
            else:
                self.txLine.setStyleSheet("QLineEdit {background-color: red}")
                self.inputTips.setText(u"<font color=red>提示: 待发送报告的ID号不存在 \n请确保第一个字节的数据是输出ID</font>")
        
    def print2TextBrowser(self, string):
        self.rxLine.insertPlainText(string)
    def itemChanged(self):
        print self.list.currentIndex(), ":", self.list.currentText()
        
        
if __name__ == '__main__':
    # saveStdout = sys.stdout
    # file = open("pyqt_hid.log","w+")
    # sys.stdout = file
    app = QtGui.QApplication(sys.argv)
    widget = MainWidget()
    widget.setWindowTitle("PyQt USB HID")
    widget.show()

    sys.exit(app.exec_())
    
    
    
    
    
    
    
    
    
    